package access

import (
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"strings"

	"chainmaker.org/chainmaker/common/v2/cert"
	bccrypto "chainmaker.org/chainmaker/common/v2/crypto"
	"chainmaker.org/chainmaker/common/v2/crypto/asym"
	"chainmaker.org/chainmaker/common/v2/crypto/pkcs11"
	"chainmaker.org/chainmaker/common/v2/crypto/sdf"
	"chainmaker.org/chainmaker/common/v2/msgbus"
	"chainmaker.org/chainmaker/logger/v2"
	"chainmaker.org/chainmaker/protocol/v2"

	nodeconfig "github.com/tokentransfer/node/config"
)

// hsmHandleMap is a global handle map for pkcs11 or sdf hsm
var hsmHandleMap = map[string]interface{}{}

// reference from chainmaker.org/chainmaker/chainmaker-go/module/accesscontrol
func InitAc(c *nodeconfig.Config, mb msgbus.MessageBus) (protocol.SigningMember, protocol.AccessControlProvider, error) {
	nodeConfig := c.NodeConfig

	log := logger.GetLoggerByChain(logger.MODULE_ACCESS, c.GetChainId())

	ac, err := NewACProvider(c, nodeConfig.OrgId, log, mb)
	if err != nil {
		return nil, nil, fmt.Errorf("new ac provider failed, %s", err.Error())
	}

	emptyAuthType := ""
	var identity protocol.SigningMember
	switch c.NodeConfig.AuthType {
	case protocol.PermissionedWithCert, protocol.Identity, emptyAuthType:
		identity, err = InitCertSigningMember(c, nodeConfig.OrgId, nodeConfig.PrivKeyFile, nodeConfig.PrivKeyPassword, nodeConfig.CertFile)
		if err != nil {
			return nil, nil, fmt.Errorf("initialize identity failed, %s", err.Error())
		}
	case protocol.PermissionedWithKey, protocol.Public:
		identity, err = InitPKSigningMember(c, nodeConfig.OrgId, nodeConfig.PrivKeyFile, nodeConfig.PrivKeyPassword)
		if err != nil {
			return nil, nil, fmt.Errorf("initialize identity failed, %s", err.Error())
		}
	default:
		err = fmt.Errorf("auth type doesn't exist")
		return nil, nil, fmt.Errorf("initialize identity failed, %s", err.Error())
	}
	return identity, ac, nil
}

func getHSMHandleId(c *nodeconfig.Config) string {
	p11Config := c.NodeConfig.P11Config
	return p11Config.Library + p11Config.Label
}

func getHSMHandle(c *nodeconfig.Config) (interface{}, error) {
	var err error
	cfg := c.NodeConfig.P11Config
	hsmKey := getHSMHandleId(c)
	handle, ok := hsmHandleMap[hsmKey]
	if !ok {
		if strings.EqualFold(cfg.Type, "pkcs11") {
			handle, err = pkcs11.New(cfg.Library, cfg.Label, cfg.Password, cfg.SessionCacheSize,
				cfg.Hash)
		} else if strings.EqualFold(cfg.Type, "sdf") {
			handle, err = sdf.New(cfg.Library, cfg.SessionCacheSize)
		} else {
			err = fmt.Errorf("invalid hsm type, want pkcs11 | sdf, got %s", cfg.Type)
		}
		if err != nil {
			return nil, fmt.Errorf("fail to initialize organization with HSM: [%v]", err)
		}
		hsmHandleMap[hsmKey] = handle
	}
	return handle, nil
}

// InitCertSigningMember 初始化一个证书签名的用户
func InitCertSigningMember(chainConfig *nodeconfig.Config, localOrgId,
	localPrivKeyFile, localPrivKeyPwd, localCertFile string) (
	protocol.SigningMember, error) {

	var certMember *certificateMember

	if localPrivKeyFile != "" && localCertFile != "" {
		certPEM, err := ioutil.ReadFile(localCertFile)
		if err != nil {
			return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
		}

		isTrustMember := false
		for _, v := range chainConfig.NodeConfig.TrustMembers {
			certBlock, _ := pem.Decode([]byte(v.MemberInfo))
			if certBlock == nil {
				return nil, fmt.Errorf("new member failed, the trsut member cert is not PEM")
			}
			if v.MemberInfo == string(certPEM) {
				certMember, err = newCertMemberFromParam(v.OrgId, v.Role,
					chainConfig.NodeConfig.HashType, false, certPEM)
				if err != nil {
					return nil, fmt.Errorf("init signing member failed, init trust member failed: [%s]", err.Error())
				}
				isTrustMember = true
				break
			}
		}

		if !isTrustMember {
			certMember, err = newMemberFromCertPem(localOrgId, chainConfig.NodeConfig.HashType, certPEM, false)
			if err != nil {
				return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
			}
		}

		skPEM, err := ioutil.ReadFile(localPrivKeyFile)
		if err != nil {
			return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
		}

		var sk bccrypto.PrivateKey
		cfg := chainConfig.NodeConfig.P11Config
		if cfg.Enabled {
			var handle interface{}
			handle, err = getHSMHandle(chainConfig)
			if err != nil {
				return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
			}
			sk, err = cert.ParseP11PrivKey(handle, skPEM)
			if err != nil {
				return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
			}
		} else {
			sk, err = asym.PrivateKeyFromPEM(skPEM, []byte(localPrivKeyPwd))
			if err != nil {
				return nil, err
			}
		}

		return &signingCertMember{
			*certMember,
			sk,
		}, nil
	}
	return nil, nil
}

// InitPKSigningMember 初始化一个公钥模式的用户
func InitPKSigningMember(chainConfig *nodeconfig.Config, localOrgId, localPrivKeyFile, localPrivKeyPwd string) (protocol.SigningMember, error) {
	if localPrivKeyFile != "" {
		skPEM, err := ioutil.ReadFile(localPrivKeyFile)
		if err != nil {
			return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
		}

		var sk bccrypto.PrivateKey
		p11Config := chainConfig.NodeConfig.P11Config
		if p11Config.Enabled {
			var handle interface{}
			handle, err = getHSMHandle(chainConfig)
			if err != nil {
				return nil, fmt.Errorf("fail to initialize identity management service: [%v]", err)
			}
			sk, err = cert.ParseP11PrivKey(handle, skPEM)
			if err != nil {
				return nil, fmt.Errorf("fail to initialize identity management service: [%v]", err)
			}
		} else {
			sk, err = asym.PrivateKeyFromPEM(skPEM, []byte(localPrivKeyPwd))
			if err != nil {
				return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
			}
		}

		publicKeyBytes, err := sk.PublicKey().Bytes()
		if err != nil {
			return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
		}

		member, err := newPkMemberFromParam(localOrgId, publicKeyBytes, protocol.Role(""), chainConfig.NodeConfig.HashType)
		if err != nil {
			return nil, fmt.Errorf("fail to initialize identity management service: [%s]", err.Error())
		}

		return &signingPKMember{
			*member,
			sk,
		}, nil
	}
	return nil, nil
}
